import { createClient, SupabaseClient } from '@supabase/supabase-js';
import winston from 'winston';

export interface UserProfile {
  userId: string;
  preferences: {
    categories: string[];
    keywords: string[];
    authors: string[];
    institutions: string[];
    frequency: string;
    topics: string[];
  };
  interactionHistory: {
    viewedPapers: string[];
    bookmarkedPapers: string[];
    downloadedPapers: string[];
    ratedPapers: { paperId: string; rating: number }[];
    clickedCategories: string[];
  };
  personaVector: number[];
  lastActiveDate: Date;
  timezone: string;
}

export interface Paper {
  id: string;
  title: string;
  abstract: string;
  authors: string[];
  arxivId: string;
  categories: string[];
  keywords: string[];
  publishedDate: string;
  url: string;
  citationCount: number;
  viewCount: number;
  downloadCount: number;
}

export interface PersonalizedContent {
  recommendedPapers: Array<Paper & {
    relevanceScore: number;
    reason: string;
    category: 'high_relevance' | 'trending' | 'author_follow' | 'similar_interest' | 'discovery';
  }>;
  trendingTopics: Array<{
    topic: string;
    paperCount: number;
    growthRate: number;
    relevanceScore: number;
  }>;
  authorUpdates: Array<{
    authorName: string;
    papers: Paper[];
    followReason: string;
  }>;
  categoryInsights: Array<{
    category: string;
    paperCount: number;
    topPapers: Paper[];
    trend: 'rising' | 'stable' | 'declining';
  }>;
  discovery: Array<{
    paper: Paper;
    discoveryReason: string;
    similarity: number;
  }>;
}

export class PersonalizationEngine {
  private supabase: SupabaseClient;
  private logger: winston.Logger;
  private userProfileCache: Map<string, { profile: UserProfile; timestamp: Date }> = new Map();
  private cacheTimeout = 30 * 60 * 1000; // 30 minutes

  constructor(supabaseUrl: string, supabaseKey: string, logger: winston.Logger) {
    this.supabase = createClient(supabaseUrl, supabaseKey);
    this.logger = logger;
  }

  /**
   * Personalize content for a specific user and template type
   */
  async personalizeContent(
    userId: string,
    templateType: string,
    baseData: Record<string, any> = {}
  ): Promise<Record<string, any>> {
    try {
      const userProfile = await this.getUserProfile(userId);
      const personalizedContent = await this.generatePersonalizedContent(userProfile, templateType);
      
      // Combine base data with personalized content
      return {
        ...baseData,
        ...personalizedContent,
        personalization: {
          userId,
          templateType,
          generatedAt: new Date().toISOString(),
          profileVersion: userProfile.personaVector.slice(0, 3).join('.')
        }
      };
    } catch (error) {
      this.logger.error(`Failed to personalize content for user ${userId}:`, error);
      // Return base data if personalization fails
      return baseData;
    }
  }

  /**
   * Get comprehensive user profile
   */
  private async getUserProfile(userId: string): Promise<UserProfile> {
    // Check cache first
    const cached = this.userProfileCache.get(userId);
    if (cached && (Date.now() - cached.timestamp.getTime()) < this.cacheTimeout) {
      return cached.profile;
    }

    try {
      // Get user basic info and preferences
      const { data: user, error: userError } = await this.supabase
        .from('users')
        .select('timezone, created_at, last_login')
        .eq('user_id', userId)
        .single();

      if (userError) throw userError;

      // Get user preferences
      const { data: preferences } = await this.supabase
        .from('user_preferences')
        .select('preference_key, preference_value')
        .eq('user_id', userId);

      // Get user subscriptions
      const { data: subscriptions } = await this.supabase
        .from('user_subscriptions')
        .select('subscription_type, subscription_target, is_active')
        .eq('user_id', userId)
        .eq('is_active', true);

      // Get interaction history
      const { data: interactions } = await this.supabase
        .from('user_paper_interactions')
        .select('paper_id, interaction_type, interaction_date')
        .eq('user_id', userId)
        .order('interaction_date', { ascending: false })
        .limit(1000);

      // Get bookmarks
      const { data: bookmarks } = await this.supabase
        .from('user_bookmarks')
        .select('paper_id, created_at')
        .eq('user_id', userId);

      // Get ratings
      const { data: ratings } = await this.supabase
        .from('user_ratings')
        .select('paper_id, rating')
        .eq('user_id', userId);

      // Get user recommendations for persona vector
      const { data: recommendations } = await this.supabase
        .from('user_recommendations')
        .select('recommendation_score, recommendation_reason')
        .eq('user_id', userId)
        .order('generated_at', { ascending: false })
        .limit(100);

      // Build user profile
      const profile: UserProfile = {
        userId,
        preferences: this.buildPreferences(preferences, subscriptions),
        interactionHistory: this.buildInteractionHistory(interactions, bookmarks, ratings),
        personaVector: this.calculatePersonaVector(interactions, ratings, recommendations),
        lastActiveDate: new Date(user.last_login || user.created_at),
        timezone: user.timezone || 'UTC'
      };

      // Cache the profile
      this.userProfileCache.set(userId, {
        profile,
        timestamp: new Date()
      });

      return profile;
    } catch (error) {
      this.logger.error(`Failed to get user profile for ${userId}:`, error);
      throw error;
    }
  }

  /**
   * Build user preferences from database data
   */
  private buildPreferences(preferences: any[], subscriptions: any[]): UserProfile['preferences'] {
    const prefs: UserProfile['preferences'] = {
      categories: [],
      keywords: [],
      authors: [],
      institutions: [],
      frequency: 'daily',
      topics: []
    };

    // Process stored preferences
    preferences?.forEach(pref => {
      if (pref.preference_key === 'research_categories') {
        prefs.categories = Array.isArray(pref.preference_value) ? pref.preference_value : [];
      } else if (pref.preference_key === 'keywords') {
        prefs.keywords = Array.isArray(pref.preference_value) ? pref.preference_value : [];
      } else if (pref.preference_key === 'email_frequency') {
        prefs.frequency = pref.preference_value || 'daily';
      }
    });

    // Process subscriptions
    subscriptions?.forEach(sub => {
      if (sub.subscription_type === 'category' && !prefs.categories.includes(sub.subscription_target)) {
        prefs.categories.push(sub.subscription_target);
      } else if (sub.subscription_type === 'author' && !prefs.authors.includes(sub.subscription_target)) {
        prefs.authors.push(sub.subscription_target);
      } else if (sub.subscription_type === 'keyword' && !prefs.keywords.includes(sub.subscription_target)) {
        prefs.keywords.push(sub.subscription_target);
      }
    });

    return prefs;
  }

  /**
   * Build interaction history from database data
   */
  private buildInteractionHistory(
    interactions: any[],
    bookmarks: any[],
    ratings: any[]
  ): UserProfile['interactionHistory'] {
    const history: UserProfile['interactionHistory'] = {
      viewedPapers: [],
      bookmarkedPapers: [],
      downloadedPapers: [],
      ratedPapers: [],
      clickedCategories: []
    };

    // Process interactions
    interactions?.forEach(interaction => {
      if (interaction.interaction_type === 'view' && !history.viewedPapers.includes(interaction.paper_id)) {
        history.viewedPapers.push(interaction.paper_id);
      } else if (interaction.interaction_type === 'download' && !history.downloadedPapers.includes(interaction.paper_id)) {
        history.downloadedPapers.push(interaction.paper_id);
      }
    });

    // Process bookmarks
    bookmarks?.forEach(bookmark => {
      if (!history.bookmarkedPapers.includes(bookmark.paper_id)) {
        history.bookmarkedPapers.push(bookmark.paper_id);
      }
    });

    // Process ratings
    ratings?.forEach(rating => {
      history.ratedPapers.push({
        paperId: rating.paper_id,
        rating: rating.rating
      });
    });

    return history;
  }

  /**
   * Calculate user persona vector based on behavior and preferences
   */
  private calculatePersonaVector(
    interactions: any[],
    ratings: any[],
    recommendations: any[]
  ): number[] {
    // Create a multi-dimensional vector representing user research interests
    const vector = new Array(50).fill(0);

    // Factor 1-10: Research area preferences (based on interactions)
    const categoryWeights = new Map<string, number>();
    interactions?.forEach(interaction => {
      // This would be enhanced with actual paper categories from the database
      categoryWeights.set('cs.AI', (categoryWeights.get('cs.AI') || 0) + 1);
    });

    // Factor 11-20: Quality preferences (based on ratings)
    let avgRating = 0;
    let ratingVariance = 0;
    if (ratings && ratings.length > 0) {
      avgRating = ratings.reduce((sum, r) => sum + r.rating, 0) / ratings.length;
      ratingVariance = ratings.reduce((sum, r) => sum + Math.pow(r.rating - avgRating, 2), 0) / ratings.length;
    }
    vector[10] = avgRating / 5; // Normalized average rating
    vector[11] = Math.min(ratingVariance / 4, 1); // Normalized rating variance

    // Factor 21-30: Engagement patterns
    vector[20] = Math.min(interactions?.length || 0, 100) / 100; // Interaction frequency
    vector[21] = Math.min((recommendations?.filter(r => r.recommendation_score > 0.8).length || 0) / 10, 1); // High-quality recommendations

    // Factor 31-40: Temporal patterns
    const now = new Date();
    const recentInteractions = interactions?.filter(i => 
      new Date(i.interaction_date) > new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000)
    ).length || 0;
    vector[30] = Math.min(recentInteractions / 20, 1); // Recent activity level

    // Factor 41-50: Discovery preferences
    const uniqueCategories = new Set(interactions?.map(i => 'category')).size; // This would be enhanced
    vector[40] = Math.min(uniqueCategories / 10, 1); // Category diversity

    return vector;
  }

  /**
   * Generate personalized content based on user profile and template type
   */
  private async generatePersonalizedContent(
    userProfile: UserProfile,
    templateType: string
  ): Promise<Partial<PersonalizedContent>> {
    try {
      switch (templateType) {
        case 'daily_digest':
          return await this.generateDailyDigestContent(userProfile);
        case 'weekly_summary':
          return await this.generateWeeklySummaryContent(userProfile);
        case 'paper_alert':
          return await this.generatePaperAlertContent(userProfile);
        default:
          return await this.generateDefaultContent(userProfile);
      }
    } catch (error) {
      this.logger.error(`Failed to generate personalized content for template ${templateType}:`, error);
      return {};
    }
  }

  /**
   * Generate daily digest content
   */
  private async generateDailyDigestContent(userProfile: UserProfile): Promise<Partial<PersonalizedContent>> {
    // Get papers from the last 24 hours
    const yesterday = new Date();
    yesterday.setDate(yesterday.getDate() - 1);

    // Get recommended papers
    const { data: recommendedPapers } = await this.supabase
      .from('user_recommendations')
      .select(`
        recommendation_score,
        recommendation_reason,
        papers (
          paper_id,
          title,
          abstract,
          arxiv_id,
          primary_category,
          secondary_categories,
          submission_date,
          citation_count,
          view_count
        )
      `)
      .eq('user_id', userProfile.userId)
      .gte('generated_at', yesterday.toISOString())
      .order('recommendation_score', { ascending: false })
      .limit(10);

    // Get papers from user's preferred categories
    const categoryPapers = await this.getPapersByCategories(
      userProfile.preferences.categories,
      yesterday,
      5
    );

    // Get trending papers
    const trendingPapers = await this.getTrendingPapers(yesterday, 3);

    // Combine and rank papers
    const allPapers = [
      ...(recommendedPapers?.map(r => ({
        ...this.formatPaper(r.papers),
        relevanceScore: Math.round(r.recommendation_score * 100),
        reason: r.recommendation_reason || 'Based on your research interests',
        category: 'high_relevance' as const
      })) || []),
      ...(categoryPapers?.map(p => ({
        ...this.formatPaper(p),
        relevanceScore: this.calculateCategoryRelevance(p, userProfile),
        reason: `Matches your interest in ${p.primary_category}`,
        category: 'similar_interest' as const
      })) || []),
      ...(trendingPapers?.map(p => ({
        ...this.formatPaper(p),
        relevanceScore: this.calculateTrendingRelevance(p, userProfile),
        reason: 'Trending in your field',
        category: 'trending' as const
      })) || [])
    ];

    // Remove duplicates and sort by relevance
    const uniquePapers = this.deduplicatePapers(allPapers)
      .sort((a, b) => b.relevanceScore - a.relevanceScore)
      .slice(0, 8);

    // Get trending topics
    const trendingTopics = await this.getTrendingTopics(userProfile, yesterday);

    return {
      recommendedPapers: uniquePapers,
      trendingTopics,
      summary: {
        totalPapers: uniquePapers.length,
        categoriesCovered: [...new Set(uniquePapers.map(p => p.categories[0]))],
        timeRange: 'Last 24 hours'
      }
    };
  }

  /**
   * Generate weekly summary content
   */
  private async generateWeeklySummaryContent(userProfile: UserProfile): Promise<Partial<PersonalizedContent>> {
    const lastWeek = new Date();
    lastWeek.setDate(lastWeek.getDate() - 7);

    // Get top papers from the week
    const { data: weeklyPapers } = await this.supabase
      .from('papers')
      .select('*')
      .gte('submission_date', lastWeek.toISOString().split('T')[0])
      .order('view_count', { ascending: false })
      .limit(20);

    // Filter and rank based on user preferences
    const relevantPapers = weeklyPapers
      ?.filter(paper => this.isRelevantToUser(paper, userProfile))
      .map(paper => ({
        ...this.formatPaper(paper),
        relevanceScore: this.calculateRelevanceScore(paper, userProfile),
        reason: this.getRelevanceReason(paper, userProfile),
        category: 'weekly_highlight' as const
      }))
      .sort((a, b) => b.relevanceScore - a.relevanceScore)
      .slice(0, 10) || [];

    // Get category insights
    const categoryInsights = await this.getCategoryInsights(userProfile, lastWeek);

    return {
      recommendedPapers: relevantPapers,
      categoryInsights,
      summary: {
        totalPapers: weeklyPapers?.length || 0,
        categoriesCovered: [...new Set(relevantPapers.map(p => p.categories[0]))],
        timeRange: 'Past 7 days'
      }
    };
  }

  /**
   * Generate paper alert content
   */
  private async generatePaperAlertContent(userProfile: UserProfile): Promise<Partial<PersonalizedContent>> {
    // Get most recent highly relevant paper
    const { data: latestPaper } = await this.supabase
      .from('user_recommendations')
      .select(`
        recommendation_score,
        recommendation_reason,
        papers (*)
      `)
      .eq('user_id', userProfile.userId)
      .gte('recommendation_score', 0.8)
      .order('generated_at', { ascending: false })
      .limit(1)
      .single();

    if (!latestPaper) {
      return {};
    }

    const formattedPaper = {
      ...this.formatPaper(latestPaper.papers),
      relevanceScore: Math.round(latestPaper.recommendation_score * 100),
      reason: latestPaper.recommendation_reason || 'Highly relevant to your research',
      category: 'high_relevance' as const
    };

    return {
      recommendedPapers: [formattedPaper]
    };
  }

  /**
   * Generate default content
   */
  private async generateDefaultContent(userProfile: UserProfile): Promise<Partial<PersonalizedContent>> {
    return {
      recommendedPapers: [],
      summary: {
        totalPapers: 0,
        categoriesCovered: userProfile.preferences.categories,
        timeRange: 'Recent'
      }
    };
  }

  /**
   * Helper methods
   */
  private async getPapersByCategories(categories: string[], since: Date, limit: number): Promise<any[]> {
    if (categories.length === 0) return [];

    const { data } = await this.supabase
      .from('papers')
      .select('*')
      .or(categories.map(cat => `primary_category.eq.${cat}`).join(','))
      .gte('submission_date', since.toISOString().split('T')[0])
      .order('citation_count', { ascending: false })
      .limit(limit);

    return data || [];
  }

  private async getTrendingPapers(since: Date, limit: number): Promise<any[]> {
    const { data } = await this.supabase
      .from('paper_analytics')
      .select(`
        trending_score,
        papers (*)
      `)
      .gte('date', since.toISOString().split('T')[0])
      .order('trending_score', { ascending: false })
      .limit(limit);

    return data?.map(item => item.papers) || [];
  }

  private async getTrendingTopics(userProfile: UserProfile, since: Date): Promise<any[]> {
    // Simplified trending topics - would be enhanced with actual topic modeling
    const topics = userProfile.preferences.categories.map(category => ({
      topic: category,
      paperCount: Math.floor(Math.random() * 20) + 5,
      growthRate: Math.random() * 0.5 + 0.1,
      relevanceScore: Math.random() * 0.5 + 0.5
    }));

    return topics.sort((a, b) => b.relevanceScore - a.relevanceScore).slice(0, 5);
  }

  private async getCategoryInsights(userProfile: UserProfile, since: Date): Promise<any[]> {
    const insights = await Promise.all(
      userProfile.preferences.categories.map(async category => {
        const { data: papers } = await this.supabase
          .from('papers')
          .select('*')
          .eq('primary_category', category)
          .gte('submission_date', since.toISOString().split('T')[0])
          .order('view_count', { ascending: false })
          .limit(3);

        return {
          category,
          paperCount: papers?.length || 0,
          topPapers: papers?.map(p => this.formatPaper(p)) || [],
          trend: Math.random() > 0.5 ? 'rising' as const : 'stable' as const
        };
      })
    );

    return insights;
  }

  private formatPaper(paper: any): Paper {
    return {
      id: paper.paper_id,
      title: paper.title,
      abstract: paper.abstract?.substring(0, 300) + (paper.abstract?.length > 300 ? '...' : ''),
      authors: ['Author 1', 'Author 2'], // Would be enhanced with actual author data
      arxivId: paper.arxiv_id,
      categories: [paper.primary_category, ...(paper.secondary_categories || [])],
      keywords: [], // Would be enhanced with keyword extraction
      publishedDate: paper.submission_date,
      url: `https://arxiv.org/abs/${paper.arxiv_id}`,
      citationCount: paper.citation_count || 0,
      viewCount: paper.view_count || 0,
      downloadCount: paper.download_count || 0
    };
  }

  private calculateCategoryRelevance(paper: any, userProfile: UserProfile): number {
    const categoryMatch = userProfile.preferences.categories.includes(paper.primary_category) ? 85 : 60;
    const keywordBoost = userProfile.preferences.keywords.some(keyword =>
      paper.title?.toLowerCase().includes(keyword.toLowerCase()) ||
      paper.abstract?.toLowerCase().includes(keyword.toLowerCase())
    ) ? 10 : 0;

    return Math.min(categoryMatch + keywordBoost, 95);
  }

  private calculateTrendingRelevance(paper: any, userProfile: UserProfile): number {
    const baseScore = 70;
    const categoryBoost = userProfile.preferences.categories.includes(paper.primary_category) ? 15 : 0;
    return Math.min(baseScore + categoryBoost, 85);
  }

  private calculateRelevanceScore(paper: any, userProfile: UserProfile): number {
    let score = 0;

    // Category match
    if (userProfile.preferences.categories.includes(paper.primary_category)) score += 40;
    if (paper.secondary_categories?.some((cat: string) => userProfile.preferences.categories.includes(cat))) score += 20;

    // Keyword match
    const titleKeywords = userProfile.preferences.keywords.filter(keyword =>
      paper.title?.toLowerCase().includes(keyword.toLowerCase())
    ).length;
    score += Math.min(titleKeywords * 15, 30);

    // Quality indicators
    if (paper.citation_count > 10) score += 10;
    if (paper.view_count > 100) score += 5;

    return Math.min(score, 100);
  }

  private isRelevantToUser(paper: any, userProfile: UserProfile): boolean {
    return this.calculateRelevanceScore(paper, userProfile) >= 50;
  }

  private getRelevanceReason(paper: any, userProfile: UserProfile): string {
    if (userProfile.preferences.categories.includes(paper.primary_category)) {
      return `Matches your interest in ${paper.primary_category}`;
    }
    
    const matchingKeywords = userProfile.preferences.keywords.filter(keyword =>
      paper.title?.toLowerCase().includes(keyword.toLowerCase())
    );
    
    if (matchingKeywords.length > 0) {
      return `Contains keywords: ${matchingKeywords.join(', ')}`;
    }

    return 'Similar to your research interests';
  }

  private deduplicatePapers(papers: any[]): any[] {
    const seen = new Set();
    return papers.filter(paper => {
      if (seen.has(paper.id)) {
        return false;
      }
      seen.add(paper.id);
      return true;
    });
  }

  /**
   * Update user persona vector based on new interactions
   */
  async updateUserPersona(userId: string, interaction: {
    type: 'view' | 'download' | 'bookmark' | 'rate' | 'click';
    paperId: string;
    rating?: number;
    category?: string;
  }): Promise<void> {
    try {
      // Remove from cache to force refresh
      this.userProfileCache.delete(userId);

      // Log interaction for future persona updates
      this.logger.info(`Updating persona for user ${userId} based on ${interaction.type} interaction`);

      // This would trigger a background job to recalculate persona vector
      // For now, we just clear the cache
    } catch (error) {
      this.logger.error(`Failed to update user persona for ${userId}:`, error);
    }
  }

  /**
   * Get personalization statistics
   */
  async getPersonalizationStats(userId: string): Promise<{
    profileCompleteness: number;
    interactionCount: number;
    categoryDiversity: number;
    engagementScore: number;
  }> {
    try {
      const userProfile = await this.getUserProfile(userId);

      const profileCompleteness = (
        (userProfile.preferences.categories.length > 0 ? 25 : 0) +
        (userProfile.preferences.keywords.length > 0 ? 25 : 0) +
        (userProfile.interactionHistory.viewedPapers.length > 0 ? 25 : 0) +
        (userProfile.interactionHistory.ratedPapers.length > 0 ? 25 : 0)
      );

      const interactionCount = 
        userProfile.interactionHistory.viewedPapers.length +
        userProfile.interactionHistory.downloadedPapers.length +
        userProfile.interactionHistory.bookmarkedPapers.length;

      const categoryDiversity = Math.min(userProfile.preferences.categories.length / 5, 1) * 100;

      const engagementScore = Math.min(
        (userProfile.interactionHistory.ratedPapers.length * 10 +
         userProfile.interactionHistory.bookmarkedPapers.length * 5 +
         userProfile.interactionHistory.viewedPapers.length) / 100,
        1
      ) * 100;

      return {
        profileCompleteness,
        interactionCount,
        categoryDiversity,
        engagementScore
      };
    } catch (error) {
      this.logger.error(`Failed to get personalization stats for ${userId}:`, error);
      return {
        profileCompleteness: 0,
        interactionCount: 0,
        categoryDiversity: 0,
        engagementScore: 0
      };
    }
  }
}